#!/usr/bin/env python3
import os
import shutil
import signal
import subprocess
import sys
import time
from pathlib import Path


def build_command(cmd: str):
    # On Windows, use shell=True for commands like 'pnpm dev'
    if os.name == "nt":
        return {"args": cmd, "shell": True}
    # On POSIX, pass as a list to avoid shell.
    return {"args": cmd.split(), "shell": False}


def start_process(name: str, cmd: str, cwd: Path):
    env = os.environ.copy()
    # Ensure local node binaries are on PATH for frontend if needed
    node_bin = cwd / "node_modules" / ".bin"
    if node_bin.exists():
        env["PATH"] = f"{str(node_bin)}{os.pathsep}{env.get('PATH','')}"

    kwargs = build_command(cmd)
    proc = subprocess.Popen(
        kwargs["args"],
        cwd=str(cwd),
        env=env,
        shell=kwargs["shell"],
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
        bufsize=1,
    )
    return name, proc


def stream_output(name: str, proc: subprocess.Popen):
    assert proc.stdout is not None
    for line in proc.stdout:
        print(f"[{name}] {line}", end="")


def terminate_process(proc: subprocess.Popen):
    if proc.poll() is not None:
        return
    try:
        if os.name == "nt":
            proc.send_signal(signal.CTRL_BREAK_EVENT)  # type: ignore[attr-defined]
            time.sleep(0.2)
        proc.terminate()
        try:
            proc.wait(timeout=5)
        except subprocess.TimeoutExpired:
            proc.kill()
    except Exception:
        try:
            proc.kill()
        except Exception:
            pass


def main():
    repo_root = Path(__file__).resolve().parents[1]

    # Configure start commands here if your stack changes
    def pick_python(candidates):
        for c in candidates:
            # On Windows, shutil.which works for simple executables; complex launchers should be avoided
            if shutil.which(c):
                return c
        # Fallback to first candidate even if not found; shell may resolve alias
        return candidates[0]

    backend_core_py = pick_python(["python310", "python3.10", "python3", "python"])  # backend prefers python310
    backend_llm_py = pick_python(["python", "python3"])  # llm_chat prefers python

    services = [
        {
            "name": "backend-core",
            "cmd": f"{backend_core_py} app.py",
            "cwd": repo_root / "backend",
        },
        {
            "name": "backend-llm",
            "cmd": f"{backend_llm_py} app.py",
            "cwd": repo_root / "LLM_Chat" / "llm_chat",
        },
        {
            "name": "frontend",
            "cmd": "pnpm dev",
            "cwd": repo_root / "frontend",
        },
    ]

    started = []
    try:
        for svc in services:
            name, proc = start_process(svc["name"], svc["cmd"], svc["cwd"])
            started.append((name, proc))
            print(f"Started {name} (pid={proc.pid}) in {svc['cwd']}")

        # Interleave outputs
        while True:
            all_done = True
            for name, proc in list(started):
                if proc.poll() is None:
                    all_done = False
                    # Non-blocking, read whatever is available line-by-line
                    if proc.stdout and not proc.stdout.closed:
                        while True:
                            line = proc.stdout.readline()
                            if not line:
                                break
                            print(f"[{name}] {line}", end="")
                else:
                    print(f"{name} exited with code {proc.returncode}")
                    started.remove((name, proc))
            if all_done:
                break
            time.sleep(0.1)
    except KeyboardInterrupt:
        print("\nReceived interrupt, terminating all services...")
    finally:
        for name, proc in started:
            print(f"Stopping {name} (pid={proc.pid})...")
            terminate_process(proc)
        print("All services stopped.")


if __name__ == "__main__":
    main()


